
/**
*	\file
*	Defines the sun::application base class.
*	\author Sean Tromnas
*/

#include <sun_pre_compiled.hpp>
#include <sun/application/application.hpp>

#include <sun/window/window.hpp>
#include <sun/time/time.hpp>
#include <sun/game_state/game_state.hpp>

#if !defined(SUN_DISABLE_LUA)
#include <sun/lua/lua.hpp>
#endif

SUN_PUSH_DISABLE_DATE_TIME_WARNINGS( )
#include <boost/date_time/posix_time/posix_time_types.hpp>
SUN_POP_DISABLED_WARNINGS( )

namespace sun
{

	//
	//	Public Member Functions
	//

	/**
	*	Initialises the application, begins the main game loop and the destroys the application.
	*	All game components required by sun are managed by this base class.
	*	@param command_line The command line arguments sent into the application.
	*	\author Sean Tromans
	*/
	void application::run( sun::command_line_type const & command_line )
	{
		if ( initialise( command_line ) )
		{
			loop( );
		}
		destroy( );
	}

	//
	//	Protected Member Functions
	//

	/**
	*	Constructor for the application object.
	*	Protected to prevent instantiation.
	*	@param step_time The length of time between each logic step.
	*	@param max_steps_per_iteration The maximum number of steps the application can make each iteration. Used to prevent near-infinite loops between stalls in the game.
	*	\author Sean Tromans
	*/
	application::application( float step_time, size_t max_steps_per_iteration )
		:		m_step_time(step_time)
			,	m_max_steps_per_iteration(max_steps_per_iteration)
			,	m_running(false)
	{
	}

	/**
	Quits the game loop and destroys the application.
	\author Sean Tromans
	*/
	void application::exit( )
	{
		m_running = false;
	}

	//
	//	Private Member Functions
	//

	/**
	*	Initialises the required sun components and calls begin_application.
	*	@param command_line The command line arguments sent into the application.
	*	@return false if the application failed to initialise.
	*	\author Sean Tromans
	*/
	bool application::initialise( sun::command_line_type const & command_line )
	{
		//	nmn::log Initialisng

		//	lua
#if !defined(SUN_DISABLE_LUA)
		if ( !lua::initialise_state( ) )
		{
			//	nmn::log failed to initialise lua
			return false;
		}
#endif

		//	input

		//	window
		window::create_singleton( );

		//	audio

#if !defined(SUN_DISABLE_LUA)
		try
		{
			luabind::globals(sun::lua::g_state)["sun"]["application"] = this;
		}
		catch ( luabind::error const & e )
		{
			log( nmn::create_string( "%1%:\n%2%", e.what( ), luabind::object( luabind::from_stack( e.state( ), -1 ) ) ) );
			return false;
		}
		catch ( std::exception const & e )
		{
			log( e.what( ) );
			return false;
		}
#endif

#if !defined(SUN_DISABLE_LUA)
		if ( !initialise_lua_interface( sun::lua::g_state ) || !begin_application( command_line ) )
		{
			//	nmn::log user initialisation failed
			return false;
		}
#else
		if ( !begin_application( command_line ) )
		{
			//	nmn::log user initialisation failed
			return false;
		}
#endif

		if ( m_state == nullptr )
		{
			//	nmn::log an initial state must be provided by begin_application
			return false;
		}

		return true;
	}

	/**
	*	Runs the main game loop at the desired step rate.
	*	\author Sean Tromans
	*/
	void application::loop( )
	{
		//	nmn::log Begining main loop

		m_running = true;

		sun::time const delta_time( m_step_time );

		boost::posix_time::ptime current_time = boost::posix_time::microsec_clock::universal_time( );
		boost::int32_t accumilator = 0;
		boost::int32_t const max_accumilator = static_cast<boost::int32_t>( m_max_steps_per_iteration * delta_time.milliseconds( ) );

		while ( m_running )
		{
			window::get_singleton( )->pump_messages( );

			boost::posix_time::ptime next_time = boost::posix_time::microsec_clock::universal_time( );
			accumilator += static_cast<boost::int32_t>( (next_time - current_time).total_milliseconds( ) );

			current_time = next_time;

			accumilator = std::min( accumilator, max_accumilator );

			for ( ; accumilator >= delta_time.milliseconds( ); accumilator -= delta_time.milliseconds( ) )
			{
				m_state = m_state->step( delta_time );
			}

			float const accumilator_seconds = static_cast<float>( accumilator ) / 1000.0f;
			float const step_fraction = accumilator_seconds / delta_time.seconds( );

			m_state->pre_draw( step_fraction, m_viewport_stack );
			m_viewport_stack.draw( step_fraction );
			window::get_singleton( )->present( );
		}

		//	nmn::log Exiting
	}

	/**
	*	Destroys the sun components created in initialise.
	*	\author Sean Tromans
	*/
	void application::destroy( )
	{
		//	nmn::log Destroying application

		m_state.reset( );

		//	audio

		//	window
		window::get_singleton( )->close_window( );
		window::destroy_singleton( );

		//	input

		//	lua
#if !defined(SUN_DISABLE_LUA)
		try
		{
			lua::destroy_state( );
		}
		catch ( lua::not_initialised const & ) {}	// swallow not_initialised exception, we must be destroying because initialisation failed.
#endif
	}

	//
	//	Static Member Functions
	//

	/**
	Exposes the application to lua.
	@param  L The lua_State being initialised.
	\author Sean Tromans
	*/
	void application::lua_interface( lua_State * L )
	{
#if !defined(SUN_DISABLE_LUA)
		luabind::module(L,"sun")
		[
			luabind::class_<sun::application>("application")
				.def( "exit", &sun::application::exit )
		];
#else
		NMN_UNUSED( L );
#endif
	}

};
